package com.sixth.sso.config;

import com.alibaba.fastjson.JSON;
import com.sixth.core.entity.LoginInfo;
import com.sixth.core.entity.Patient;
import com.sixth.core.entity.User;
import com.sixth.core.util.JWTUtil;
import com.sixth.core.vo.R;
import com.sixth.sso.feign.DictDateFeign;
import com.sixth.sso.feign.LoginFeign;
import com.sixth.sso.feign.UserFeign;
import com.sixth.sso.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.annotation.Resource;
import java.io.PrintWriter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Resource
    private MyUserDetailService userDetailService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserFeign userFeign;

    @Autowired
    private LoginFeign loginFeign;

    @Autowired
    private DictDateFeign dictDateFeign;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginProcessingUrl("/sso/login")
                .successHandler(successHandler())
                .failureHandler(failureHandler())
                .permitAll();

        http.csrf().disable();

        http.logout()
                .logoutUrl("/sso/logout")
                .logoutSuccessHandler((httpServletRequest, httpServletResponse, e) -> {
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    PrintWriter writer = httpServletResponse.getWriter();
                    String sourceClient = httpServletRequest.getHeader("source-client");
                    String token = httpServletRequest.getHeader("token");
                    if ("miniapp".equalsIgnoreCase(sourceClient)) {
                        if (token != null && !token.isEmpty()) {
                            redisTemplate.delete("clientLogin:" + token);
                        }
                        R<String> r = R.ok("200");
                        String jsonString = JSON.toJSONString(r);
                        writer.println(jsonString);
                    }else {
                        if (token != null && !token.isEmpty()) {
                            redisTemplate.delete("login:" + token);
                        }
                        R<String> r = R.ok("200");
                        String jsonString = JSON.toJSONString(r);
                        writer.println(jsonString);
                    }
                    writer.flush();
                    writer.close();

                });

        http.cors();

        http.authorizeRequests().anyRequest().authenticated();
    }

    private AuthenticationFailureHandler failureHandler() {
        return (httpServletRequest, httpServletResponse, e) -> {
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();

            String sourceClient = httpServletRequest.getHeader("source-client");
            String userTelephone = httpServletRequest.getParameter("username");

            // 获取其他信息
            String ipAddr = httpServletRequest.getRemoteAddr();
            String browserRaw = httpServletRequest.getHeader("User-Agent");
            String browser = extractBrowserNameRegex(browserRaw);
            String os = System.getProperty("os.name");

            // 根据source-client处理不同逻辑
            if ("miniapp".equalsIgnoreCase(sourceClient)) {
                // 获取用户信息
                R<Patient> userResponse = userFeign.getByNamePatient(userTelephone);
                Patient patient = userResponse.getData();
                if (patient != null) {
                    // 记录失败登录信息
                    LoginInfo loginInfo = new LoginInfo();
                    loginInfo.setLoginType("2"); // miniapp登录失败
                    loginInfo.setUserName(patient.getName());
                    loginInfo.setLoginAccount(patient.getPhone());
                    loginInfo.setIpAddr(ipAddr);
                    loginInfo.setBrowser(browser);
                    loginInfo.setLoginLocation("内网IP");
                    loginInfo.setOs(os);
                    loginInfo.setLoginStatus("1"); // 失败
                    loginInfo.setLoginTime(new Date());
                    loginFeign.save(loginInfo);
                    // Miniapp客户端的失败处理
                    R<Object> r = R.error("miniapp登录失败");
                    writer.println(JSON.toJSONString(r));
                }
                // Miniapp客户端的失败处理
                System.out.println("获取的用户信息为空");
                R<Object> r = R.error("患者用户信息为空");
                writer.println(JSON.toJSONString(r));

            } else {

                // 获取用户信息
                R<User> userResponse = userFeign.getByPhone(userTelephone);
                User user = userResponse.getData();
                if (user != null) {
                    String userName = user.getUserName();
                    // 记录失败登录信息
                    LoginInfo loginInfo = new LoginInfo();
                    loginInfo.setLoginType("1"); // 普通登录失败
                    loginInfo.setUserName(userName);
                    loginInfo.setLoginAccount(userTelephone);
                    loginInfo.setIpAddr(ipAddr);
                    loginInfo.setBrowser(browser);
                    loginInfo.setLoginLocation("内网IP");
                    loginInfo.setOs(os);
                    loginInfo.setLoginStatus("1"); // 失败
                    loginInfo.setLoginTime(new Date());
                    loginFeign.save(loginInfo);
                    // Miniapp客户端的失败处理
                    R<Object> r = R.error("登录失败");
                    writer.println(JSON.toJSONString(r));
                }
                // Miniapp客户端的失败处理
                R<Object> r = R.error("用户信息为空");
                writer.println(JSON.toJSONString(r));
            }

            // 完成输出并关闭writer
            writer.flush();
            writer.close();
        };
    }

    private AuthenticationSuccessHandler successHandler() {
        return (httpServletRequest, httpServletResponse, authentication) -> {
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();

            String username = authentication.getName();
            String sourceClient = httpServletRequest.getHeader("source-client");

            Map<String, Object> map = new HashMap<>();
            map.put("username", username);

            if ("miniapp".equalsIgnoreCase(sourceClient)) {
                R<Patient> patientR = userFeign.getByNamePatient(username);
                if (patientR.getCode() != 200) {
                    R<Object> errorResponse = R.error("获取用户信息失败");
                    writer.println(JSON.toJSONString(errorResponse));
                    writer.flush();
                    writer.close();
                    return;
                }

                Patient patient = patientR.getData();
                Map<String, Object> mapClient = new HashMap<>();
                mapClient.put("name", patient.getName());
                mapClient.put("patientId", patient.getPatientId());
                mapClient.put("phone", patient.getPhone());
                mapClient.put("avatar", patient.getAvatar());
                mapClient.put("sex", patient.getSex());
                mapClient.put("birthday", patient.getBirthday());
                mapClient.put("idCard", patient.getIdCard());
                mapClient.put("allergyInfo", patient.getAllergyInfo());
                mapClient.put("address", patient.getAddress());
                mapClient.put("isFinal", patient.getIsFinal());
                mapClient.put("lastLoginIp", patient.getLastLoginIp());
                mapClient.put("lastLoginTime", patient.getLastLoginTime());

                String token = JWTUtil.createToken(mapClient);
                redisTemplate.opsForValue().set("clientLogin:" + token, "", 24, TimeUnit.HOURS);
                mapClient.put("token", token);
                R<Object> ok = R.ok(mapClient);
                writer.println(JSON.toJSONString(ok));

                // Log successful login
                String browserRaw = httpServletRequest.getHeader("source-client");
                String browser = extractBrowserNameRegex(browserRaw);
                String os = System.getProperty("os.name");
                String ipAddr = httpServletRequest.getRemoteAddr();

                LoginInfo loginInfo = getLoginInfo(patient, browser, os,ipAddr);
                loginFeign.save(loginInfo);

            } else {
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                List<String> collect = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
                map.put("permissions", collect);

                String token = JWTUtil.createToken(map);
                redisTemplate.opsForValue().set("login:" + token, "", 30, TimeUnit.MINUTES);
                R<Object> ok = R.ok(token);
                writer.println(JSON.toJSONString(ok));

                // Log successful login
                UserDetails user = (UserDetails) authentication.getPrincipal();
                String userTelephone = user.getUsername();
                R<User> byPhone = userFeign.getByPhone(userTelephone);
                User data = byPhone.getData();
                String loginUsername = userFeign.getByPhone(userTelephone).getData().getUserName();

                String ipAddr = httpServletRequest.getRemoteAddr();
                String browserRaw = httpServletRequest.getHeader("User-Agent");
                String browser = extractBrowserNameRegex(browserRaw);
                String os = System.getProperty("os.name");

                LoginInfo loginInfo = new LoginInfo();
                if (data.getUserType().equals("1")){
                    loginInfo.setLoginType("1");
                }else {
                    loginInfo.setLoginType("0");
                }
                loginInfo.setUserName(loginUsername);
                loginInfo.setLoginAccount(userTelephone);
                loginInfo.setIpAddr(ipAddr);
                loginInfo.setBrowser(browser);
                loginInfo.setLoginLocation("内网IP");
                loginInfo.setOs(os);
                loginInfo.setLoginStatus("0"); // 成功
                loginInfo.setLoginTime(new Date());
                loginFeign.save(loginInfo);
            }

            writer.flush();
            writer.close();
        };
    }

    private static LoginInfo getLoginInfo(Patient patient, String browser, String os, String ipAddr) {
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setLoginType("2");
        loginInfo.setUserName(patient.getName());
        loginInfo.setLoginAccount(patient.getPhone());
        loginInfo.setIpAddr(ipAddr);
        loginInfo.setBrowser(browser);
        loginInfo.setLoginLocation("内网IP");
        loginInfo.setOs(os);
        loginInfo.setLoginStatus("0"); // 成功
        loginInfo.setLoginTime(new Date());
        return loginInfo;
    }

    public String extractBrowserNameRegex(String userAgent) {
        if (userAgent.contains("Chrome")) {
            return "Chrome";
        } else if (userAgent.contains("Firefox")) {
            return "Firefox";
        } else if (userAgent.contains("Safari")) {
            return "Safari";
        } else if (userAgent.contains("Edge")) {
            return "Edge";
        } else {
            return "Other";
        }
    }
}
